package btstack

/*
#cgo CFLAGS: -I ./include -I ./include/src -I ./include/src/platform/windows
#cgo LDFLAGS: -static -L./libbtstack -lbtstack -lsetupapi -lwinusb

#include "btstack_config.h"
#include "btstack_run_loop_windows.h"

#include "btstack.h"

void PacketCallback(uint8_t packetType, uint16_t channel, uint8_t *packet, uint16_t size);
void AttPacketHandler(uint8_t packetType, uint16_t channel, uint8_t *packet, uint16_t size);
int attWriteCallbackC(hci_con_handle_t con_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size);
void stdinProcessC(char cmd);
*/
import "C"
import (
	"bytes"
	"errors"
	"net"
	"strings"
	"time"
	"unsafe"
)

type HciPowerMode int

const (
	HciPowerOff HciPowerMode = iota
	HciPowerOn
	HciPowerSleep
)

type IoCapabilityType int

const (
	IoCapabilityDisplayOnly IoCapabilityType = iota
	IoCapabilityDisplayYesNo
	IoCapabilityKeyboardOnly
	IoCapabilityNoInputNoOutput
	IoCapabilityKeyboardDisplay
)

type HciState int

const (
	HciStateOff HciState = iota
	HciStateInitializing
	HciStateWorking
	HciStateHalting
	HciStateSleeping
	HciStateFallingAsleep
)

type BdAddrType int

func (t BdAddrType) String() string {
	switch t {
	case LePublicAddr:
		return "PublicAddr"
	case LeRandomAddr:
		return "RandomAddr"
	case LePublicIdentityAddr:
		return "PublicIdentityAddr"
	case LeRandomIdentityAddr:
		return "RandomIdentityAddr"
	default:
		return "Unknown"
	}
}

const (
	LePublicAddr BdAddrType = iota
	LeRandomAddr
	LePublicIdentityAddr
	LeRandomIdentityAddr
)

type AdvType uint8

func (t AdvType) String() string {
	switch t {
	case AdvInd:
		return "AdvInd"
	case AdvDirectInd:
		return "AdvDirectInd"
	case AdvScanInd:
		return "AdvScanInd"
	case AdvNonconnInd:
		return "AdvNonconnInd"
	case ScanResp:
		return "ScanResp"
	default:
		return "Unknown"
	}
}

const (
	AdvInd AdvType = iota
	AdvDirectInd
	AdvScanInd
	AdvNonconnInd
	ScanResp
)

type AdFlags uint8

func (f AdFlags) String() string {
	var info []string

	if f & FlagBitLeLimitedDiscoverableMode > 0 {
		info = append(info, "LeLimitedDiscoverableMode")
	}

	if f & FlagBitLeGeneralDiscoverableMode > 0 {
		info = append(info, "LeGeneralDiscoverableMode")
	}

	if f & FlagBitBrEdrNotSupported > 0 {
		info = append(info, "BrEdrNotSupported")
	}

	if f & FlagBitLeBrEdrController > 0 {
		info = append(info, "LeBrEdrController")
	}

	if f & FlagBitLeBrEdrHost > 0 {
		info = append(info, "LeBrEdrHost")
	}

	return strings.Join(info, "|")
}

const (
	FlagBitLeLimitedDiscoverableMode = iota << 0
	FlagBitLeGeneralDiscoverableMode
	FlagBitBrEdrNotSupported
	FlagBitLeBrEdrController
	FlagBitLeBrEdrHost
)

const (
	HciDumpLogLevelDebug = 0
	HciDumpLogLevelInfo  = 1
	HciDumpLogLevelError = 2
)

type HciRole uint8

func (r HciRole) String() string {
	switch r {
	case HciRoleMaster:
		return "Master"
	case HciRoleSlave:
		return "Slave"
	default:
		return "Unknown"
	}
}

const (
	HciRoleMaster HciRole = iota
	HciRoleSlave
)

type GapRandomAddressType uint8

const (
	GapRandomAddressTypeOff = iota
	GapRandomAddressTypeStatic
	GapRandomAddressNonResolvable
	GapRandomAddressResolvable
)

type HciDumpFormat uint8

const (
	HciDumpBluez HciDumpFormat = iota
	HciDumpPacketlogger
	HciDumpStdout
)

type GattClientServiceType struct {
	s C.gatt_client_service_t
}

func (s *GattClientServiceType) UUID16() uint16 {
	return uint16(s.s.uuid16)
}

type GattClientCharacteristicType struct {
	c C.gatt_client_characteristic_t
}

func (c *GattClientCharacteristicType) UUID16() uint16 {
	return uint16(c.c.uuid16)
}

func (c *GattClientCharacteristicType) ValueHandle() uint16 {
	return uint16(c.c.value_handle)
}


func MemoryInit() {
	C.btstack_memory_init()
}

func RunLoopWindowsGetInstance() unsafe.Pointer {
	return unsafe.Pointer(C.btstack_run_loop_windows_get_instance())
}

func RunLoopInit(runLoop unsafe.Pointer) {
	C.btstack_run_loop_init((*C.btstack_run_loop_t)(runLoop))
}

func HciTransportUsbInstance() unsafe.Pointer {
	return unsafe.Pointer(C.hci_transport_usb_instance())
}

func HciInit(transport unsafe.Pointer, config unsafe.Pointer) {
	C.hci_init((*C.hci_transport_t)(transport), config)
}

func L2capInit() {
	C.l2cap_init()
}

func LeDeviceDbInit() {
	C.le_device_db_init()
}

func SmSetIoCapabilities(ioCapability IoCapabilityType) {
	C.sm_set_io_capabilities(C.io_capability_t(ioCapability))
}

func GattClientInit() {
	C.gatt_client_init()
}

func SmInit() {
	C.sm_init()
}

func RunLoopExecute() {
	var notification C.gatt_client_notification_t
	C.gatt_client_listen_for_characteristic_value_updates(&notification, C.btstack_packet_handler_t(C.PacketCallback),
		C.GATT_CLIENT_ANY_CONNECTION, nil)

	C.att_server_register_packet_handler(C.btstack_packet_handler_t(C.AttPacketHandler))

	C.btstack_stdin_setup((*[0]byte)(C.stdinProcessC))

	C.btstack_run_loop_execute()
}

func GapSetScanParameters(scanType uint8, scanInterval uint16, scanWindow uint16) {
	C.gap_set_scan_parameters(C.uint8_t(scanType), C.uint16_t(scanInterval), C.uint16_t(scanWindow))
}

func GapStartScan() {
	C.gap_start_scan()
}

func HciPowerControl(mode HciPowerMode) {
	C.hci_power_control(C.HCI_POWER_MODE(mode))
}

func GapConnect(addr interface{}, addrType BdAddrType) error {
	var naddr net.HardwareAddr

	switch addr := addr.(type) {
	case net.HardwareAddr:
		naddr = addr
	case []byte:
		naddr = addr[:6]
	case string:
		var err error
		naddr, err = net.ParseMAC(addr)
		if err != nil {
			return err
		}
	default:
		return errors.New("invalid argument")
	}

	a := C.CBytes(naddr)
	defer C.free(a)

	C.gap_connect((*C.uint8_t)(a), C.bd_addr_type_t(addrType))

	return nil
}

func HciDumpEnableLogLevel(logLevel int32, enable int32) {
	C.hci_dump_enable_log_level(C.int(logLevel), C.int(enable))
}

func GapStopScan()  {
	C.gap_stop_scan()
}

func DiscoverPrimaryServicesByUUID16(handle, uuid16 uint16) {
	C.gatt_client_discover_primary_services_by_uuid16(C.btstack_packet_handler_t(C.PacketCallback), C.uint16_t(handle), C.uint16_t(uuid16))
}

func DiscoverCharacteristicsForServiceByUUID16(handle uint16, service *GattClientServiceType, uuid16 uint16)  {
	C.gatt_client_discover_characteristics_for_service_by_uuid16(C.btstack_packet_handler_t(C.PacketCallback),
		C.uint16_t(handle), &service.s, C.uint16_t(uuid16))
}

type ValueUpdatesCallbackType func(valueHandle uint16, value []byte)

var ValueUpdatesCallbacks = make(map[uint16]ValueUpdatesCallbackType)

func ListenForCharacteristicValueUpdates(conHandle uint16, characteristic *GattClientCharacteristicType, callback ValueUpdatesCallbackType) {
	ValueUpdatesCallbacks[characteristic.ValueHandle()] = callback
	C.gatt_client_write_client_characteristic_configuration(C.btstack_packet_handler_t(C.PacketCallback),
		C.uint16_t(conHandle), &characteristic.c, C.uint16_t(C.GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION))
}

func WriteValueOfCharacteristicWithoutResponse(conHandle uint16, valueHandle uint16, value []byte)  {
	p := C.CBytes(value)
	defer C.free(p)

	C.gatt_client_write_value_of_characteristic_without_response(C.uint16_t(conHandle),
		C.uint16_t(valueHandle), C.uint16_t(len(value)), (*C.uint8_t)(p))
}

func HciClose()  {
	C.hci_close()
}

var attWriteCallback func(conHandle, attHandle uint16, value []byte)

//export attWriteCallbackC
func attWriteCallbackC(conHandle C.hci_con_handle_t, attHandle, transactionMode, offset C.uint16_t, buffer *C.uint8_t,
	bufferSize C.uint16_t) C.int {

	if attWriteCallback == nil {
		return 0
	}

	attWriteCallback(uint16(conHandle), uint16(attHandle), C.GoBytes(unsafe.Pointer(buffer), C.int(bufferSize)))

	return 0
}

func StartAdvertising(name string, profileData []byte, writeCallback func(conHandle, attHandle uint16, value []byte))  {
	cProfileData := C.CBytes(profileData)
	C.att_server_init((*C.uint8_t)(cProfileData), C.att_read_callback_t(nil), C.att_write_callback_t(C.attWriteCallbackC))

	attWriteCallback = writeCallback

	var nullAddr = make([]byte, 6)
	cNullAddr := C.CBytes(nullAddr)
	defer C.free(cNullAddr)
	C.gap_advertisements_set_params(0x30, 0x30, 0, 0, (*C.uint8_t)(cNullAddr), 0x07, 0x00)

	advdata := bytes.Buffer{}

	// Flags general discoverable, BR/ EDR not supported
	advdata.Write([]byte{0x02, C.BLUETOOTH_DATA_TYPE_FLAGS, 0x06})

	advdata.Write([]byte{(byte)(len(name) + 1), C.BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME})
	advdata.WriteString(name)

	// Incomplete List of 16-bit Service Class UUIDs -- FF10 - only valid for testing!
	advdata.Write([]byte{0x03, C.BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 0x10, 0xff})

	cAdvdata := C.CBytes(advdata.Bytes())
	time.AfterFunc(time.Second, func() {
		C.free(cAdvdata)
	})

	C.gap_advertisements_set_data(C.uint8_t(len(advdata.Bytes())), (*C.uint8_t)(cAdvdata))

	C.gap_advertisements_enable(1)
}

func StopAdvertising()  {
	C.gap_advertisements_enable(0)
}

func GapReadRssi(conHandle uint16)  {
	C.gap_read_rssi(C.uint16_t(conHandle))
}

func GapLocalBdAddr() net.HardwareAddr {
	var addressBuffer = C.malloc(C.BD_ADDR_LEN)
	defer C.free(addressBuffer)

	C.gap_local_bd_addr((*C.uint8_t)(addressBuffer))

	return C.GoBytes(addressBuffer, C.BD_ADDR_LEN)
}

var stdinProcess func(cmd byte)

//export stdinProcessC
func stdinProcessC(cmd C.char) {
	if stdinProcess == nil {
		return
	}

	stdinProcess(byte(cmd))
}

func StdinSetup(cb func(cmd byte)) {
	stdinProcess = cb
}

func AttServerRequestCanSendNowEvent(conHandle uint16)  {
	C.att_server_request_can_send_now_event(C.uint16_t(conHandle))
}

func GapDisconnect(handle uint16) {
	C.gap_disconnect(C.uint16_t(handle))
}

func HciDisconnectAll() {
	C.hci_disconnect_all()
}

func GapRandomAddressGetMode() GapRandomAddressType {
	return GapRandomAddressType(C.gap_random_address_get_mode())
}

func GapRandomAddressSetMode(mode GapRandomAddressType)  {
	C.gap_random_address_set_mode(C.gap_random_address_type_t(mode))
}

func HciDumpOpen(filename string, format HciDumpFormat)  {
	cfilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cfilename))
	C.hci_dump_open(cfilename, C.hci_dump_format_t(format))
}
